package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"os/exec"
	"github.com/spf13/pflag"
)

type selpgArgs struct {
    startPage  int
    endPage    int
    pageLen    int
    pageType   bool
    inFileName string
    printDest  string
}
func main() {
	var args selpgArgs
	getArgs(&args) // 读取
    checkArgs(&args) // 判断合法
	excuteCMD(&args) // 执行任务
}

func getArgs(args *selpgArgs) {
    // 获取-s -e -l -f -d 参数
    pflag.IntVarP(&(args.startPage), "startPage", "s", -1, "Define startPage")
    pflag.IntVarP(&(args.endPage), "endPage", "e", -1, "Define endPage")
    pflag.IntVarP(&(args.pageLen), "pageLength", "l", 72, "Define pageLength")
    pflag.StringVarP(&(args.printDest), "printDest", "d", "", "Define printDest")
    pflag.BoolVarP(&(args.pageType), "pageType", "f", false, "Define pageType")
    pflag.Parse()
    
    // 获取 filename 参数
    filename := pflag.Args()
    if len(filename) > 0 {
        args.inFileName = string(filename[0])
    } else {
        args.inFileName = ""
    }
}

func checkArgs(args *selpgArgs) {
    // 判断输入参数合法性
    if (args.startPage == -1) || (args.endPage == -1) {
        fmt.Fprintf(os.Stderr, "[Error]The startPage and endPage can't be empty!\n")
        os.Exit(1)
    } else if (args.startPage <= 0) || (args.endPage <= 0) {
        fmt.Fprintf(os.Stderr, "[Error]The startPage and endPage can't be less than 1!\n")
        os.Exit(2)
    } else if args.startPage > args.endPage {
        fmt.Fprintf(os.Stderr, "[Error]The startPage can't be bigger than the endPage!\n")
        os.Exit(3)
    } else if (args.pageType == true) && (args.pageLen != 72) {
        fmt.Fprintf(os.Stderr, "[Error]The command -l and -f are exclusive!\n")
        os.Exit(4)
    } else if args.pageLen <= 0 {
        fmt.Fprintf(os.Stderr, "[Error]The pageLen can't be less than 1 !\n")
        os.Exit(5)
    } else {
        // 输入参数均合法，判断是选择了-l还是-f，并输入参数列表。
        pageType := "page length."
        if args.pageType == true {
            pageType = "The end sign /f."
        }
        fmt.Printf("[ArgsStart]\n")
        fmt.Printf("startPage: %d\nendPage: %d\ninputFile: %s\npageLength: %d\npageType: %s\nprintDestation: %s\n[ArgsEnd]\n", args.startPage, args.endPage, args.inFileName, args.pageLen, pageType, args.printDest)
    }
}

func excuteCMD(args *selpgArgs) {
    var inp *os.File
    if args.inFileName == "" { // 从标准输入中读取
        inp = os.Stdin
    } else { // 从文件中读取
        // 检测文件是否可读取
        checkFileAccess(args.inFileName)
        var err error
        inp, err = os.Open(args.inFileName)
        // 检测文件是否打开成功
        checkError(err, "input file")
    }
 
    if len(args.printDest) == 0 { // 输出到标准输出
        output(os.Stdout, inp, args.startPage, args.endPage, args.pageLen, args.pageType)
    } else { // 输出到文件中
        output(getDesio(args.printDest), inp, args.startPage, args.endPage, args.pageLen, args.pageType)
    }
}

func checkError(err error, object string) {
    if err != nil {
        fmt.Fprintf(os.Stderr, "[Error]%s:", object)
        panic(err)
    }
}

func checkFileAccess(filename string) {
    _, errFileExits := os.Stat(filename)
    // 检查是否不存在此文件
    if os.IsNotExist(errFileExits) {
        fmt.Fprintf(os.Stderr, "[Error]: input file \"%s\" does not exist\n", filename)
        os.Exit(6)
    }
}

func getDesio(printDest string) io.WriteCloser {
    cmd := exec.Command("lp", "-d"+printDest)
    // 将命令行的输入管道stdinpipe获取到的指针赋给fout
    fout, err := cmd.StdinPipe()
    checkError(err, "StdinPipe")
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    errStart := cmd.Run()
    checkError(errStart, "CMD run")
    // 将fout返回给output函数作为输出地址参数
    return fout
}

func output(fout interface{}, inp *os.File, pageStart int, pageEnd int, pageLen int, pageType bool) {
    lineCount := 0
    pageCount := 1
    buf := bufio.NewReader(inp)

    for true {
        var line string
        var err error
        if pageType {
            // -f的情况
            line, err = buf.ReadString('\f')
            pageCount++
        } else {
            // -l的情况
            line, err = buf.ReadString('\n')
            lineCount++
            if lineCount > pageLen {
                pageCount++
                lineCount = 1
            }
        }
        if err == io.EOF { // 已经读完
            break
        }
        // 检测读出有无出错
        checkError(err, "file read in")

        // 判断是不是在需要输出的内容的范围内
        if (pageCount >= pageStart) && (pageCount <= pageEnd) {
            var outputErr error
            // 通过类型断言，判断fout的类型，知道应该调用哪个函数
            if stdOutput, ok := fout.(*os.File); ok {
                _, outputErr = fmt.Fprintf(stdOutput, "%s", line)
            } else if pipeOutput, ok := fout.(io.WriteCloser); ok {
                _, outputErr = pipeOutput.Write([]byte(line))
            } else {
                fmt.Fprintf(os.Stderr, "[Error]:fout type error.")
                os.Exit(7)
            }
            // 检测输出有无出错
            checkError(outputErr, "Error happend when output the pages.")
        }
    }

    if pageCount < pageStart { // 开始页太大
        fmt.Fprintf(os.Stderr, "[Error]: startPage (%d) greater than total pages (%d)\n", pageStart, pageCount)
        os.Exit(8)
    } else if pageCount < pageEnd { // 结束页太大
        fmt.Fprintf(os.Stderr, "[Error]: endPage (%d) greater than total pages (%d)\n", pageEnd, pageCount)
        os.Exit(9)
    }
}